﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;

using System.Reflection;
using System.ComponentModel;

namespace MishraReader
{
    // The code in this file is from Dustin Campbell's blog at
    // http://diditwith.net/PermaLink,guid,aacdb8ae-7baa-4423-a953-c18c1c7940ab.aspx
    /// <summary>
    /// </summary>
    /// <typeparam name="E"> </typeparam>
    /// <param name="eventHandler"> </param>
    public delegate void UnregisterEventHandler<E>(EventHandler<E> eventHandler) where E : EventArgs;

    /// <summary>
    /// </summary>
    /// <param name="eventHandler"> </param>
    public delegate void UnregisterPropertyChangedEventHandler(PropertyChangedEventHandler eventHandler);


    /// <summary>
    /// </summary>
    /// <typeparam name="E"> </typeparam>
    internal interface IWeakEventHandler<E> where E : EventArgs
    {
        EventHandler<E> Handler { get; }
        WeakReference Target { get; }
    }

    /// <summary>
    /// </summary>
    /// <typeparam name="E"> </typeparam>
    internal interface IWeakPropertyChangedEventHandler
    {
        PropertyChangedEventHandler Handler { get; }
        WeakReference Target { get; }
    }

    /// <summary>
    ///   Weak Event Handler, taken from: http://diditwith.net/PermaLink,guid,aacdb8ae-7baa-4423-a953-c18c1c7940ab.aspx
    /// </summary>
    /// <typeparam name="T"> </typeparam>
    /// <typeparam name="E"> </typeparam>
    internal class WeakEventHandler<T, E> : IWeakEventHandler<E>
        where T : class
        where E : EventArgs
    {
        private readonly EventHandler<E> m_Handler;
        private readonly OpenEventHandler m_OpenHandler;
        private readonly WeakReference m_TargetRef;
        private UnregisterEventHandler<E> m_Unregister;

        /// <summary>
        /// </summary>
        /// <param name="eventHandler"> </param>
        /// <param name="unregister"> </param>
        public WeakEventHandler(EventHandler<E> eventHandler, UnregisterEventHandler<E> unregister)
        {
            m_TargetRef = new WeakReference(eventHandler.Target);

            var mi = eventHandler.GetMethodInfo();

            m_OpenHandler = (OpenEventHandler)mi.CreateDelegate(typeof(OpenEventHandler), null);

            m_Handler = Invoke;
            m_Unregister = unregister;
        }



        /// <summary>
        /// </summary>
        public EventHandler<E> Handler
        {
            get { return m_Handler; }
        }

        /// <summary>
        /// </summary>
        public WeakReference Target
        {
            get { return m_TargetRef; }
        }

        /// <summary>
        /// </summary>
        /// <param name="sender"> </param>
        /// <param name="e"> </param>
        public void Invoke(object sender, E e)
        {
            var target = (T)m_TargetRef.Target;

            if (!ReferenceEquals(target, null))
                m_OpenHandler.Invoke(target, sender, e);
            else if (m_Unregister != null)
            {
                m_Unregister(m_Handler);
                m_Unregister = null;
            }
        }

        /// <summary>
        /// </summary>
        /// <param name="weh"> </param>
        /// <returns> </returns>
        public static implicit operator EventHandler<E>(WeakEventHandler<T, E> weh)
        {
            return weh.m_Handler;
        }

        private delegate void OpenEventHandler(T @this, object sender, E e);
    }

    /// <summary>
    ///   Weak Event Handler, taken from: http://diditwith.net/PermaLink,guid,aacdb8ae-7baa-4423-a953-c18c1c7940ab.aspx
    /// </summary>
    /// <typeparam name="T"> </typeparam>
    internal class WeakPropertyChangedEventHandler<T> : IWeakPropertyChangedEventHandler
        where T : class
    {
        private readonly PropertyChangedEventHandler m_Handler;
        private readonly OpenEventHandler m_OpenHandler;
        private readonly WeakReference m_TargetRef;
        private UnregisterPropertyChangedEventHandler m_Unregister;

        /// <summary>
        /// </summary>
        /// <param name="eventHandler"> </param>
        /// <param name="unregister"> </param>
        public WeakPropertyChangedEventHandler(PropertyChangedEventHandler eventHandler, UnregisterPropertyChangedEventHandler unregister)
        {
            m_TargetRef = new WeakReference(eventHandler.Target);

            var mi = eventHandler.GetMethodInfo();

            m_OpenHandler = (OpenEventHandler)mi.CreateDelegate(typeof(OpenEventHandler), null);

            m_Handler = Invoke;
            m_Unregister = unregister;
        }



        /// <summary>
        /// </summary>
        public PropertyChangedEventHandler Handler
        {
            get { return m_Handler; }
        }

        /// <summary>
        /// </summary>
        public WeakReference Target
        {
            get { return m_TargetRef; }
        }

        /// <summary>
        /// </summary>
        /// <param name="sender"> </param>
        /// <param name="e"> </param>
        public void Invoke(object sender, PropertyChangedEventArgs e)
        {
            var target = (T)m_TargetRef.Target;

            if (!ReferenceEquals(target, null))
                m_OpenHandler.Invoke(target, sender, e);
            else if (m_Unregister != null)
            {
                m_Unregister(m_Handler);
                m_Unregister = null;
            }
        }

        /// <summary>
        /// </summary>
        /// <param name="weh"> </param>
        /// <returns> </returns>
        public static implicit operator PropertyChangedEventHandler(WeakPropertyChangedEventHandler<T> weh)
        {
            return weh.m_Handler;
        }

        private delegate void OpenEventHandler(T @this, object sender, PropertyChangedEventArgs e);
    }


    /// <summary>
    /// </summary>
    public static class EventHandlerUtils
    {
        /// <summary>
        /// </summary>
        /// <typeparam name="E"> </typeparam>
        /// <param name="eventHandler"> </param>
        /// <param name="unregister"> </param>
        /// <returns> </returns>
        public static EventHandler<E> MakeWeak<E>(this EventHandler<E> eventHandler, UnregisterEventHandler<E> unregister) where E : EventArgs
        {
            Contract.Requires<ArgumentNullException>(eventHandler != null, "eventHandler");
            Contract.Requires<ArgumentException>(!eventHandler.GetMethodInfo().IsStatic && eventHandler.Target != null, "Only instance methods are supported.");

            var method = eventHandler.GetMethodInfo();
            // check to see if we're already weak
            if (method.DeclaringType.GetTypeInfo().IsGenericType && method.DeclaringType.GetGenericTypeDefinition() == typeof(WeakEventHandler<,>))
            {
                return eventHandler;
            }

            var wehType = typeof(WeakEventHandler<,>).MakeGenericType(method.DeclaringType, typeof(E));

            var wehConstructor =wehType.GetTypeInfo().DeclaredConstructors.First();

            var weh = (IWeakEventHandler<E>)wehConstructor.Invoke(
                new object[] {eventHandler, unregister});

            return weh.Handler;
        }

        /// <summary>
        /// </summary>
        /// <typeparam name="E"> </typeparam>
        /// <param name="eventHandler"> </param>
        /// <param name="unregister"> </param>
        /// <returns> </returns>
        public static PropertyChangedEventHandler MakeWeak(this PropertyChangedEventHandler eventHandler, UnregisterPropertyChangedEventHandler unregister)
        {
            Contract.Requires<ArgumentNullException>(eventHandler != null, "eventHandler");
            Contract.Requires<ArgumentException>(!eventHandler.GetMethodInfo().IsStatic && eventHandler.Target != null, "Only instance methods are supported.");

            var method = eventHandler.GetMethodInfo();
            // check to see if we're already weak
            if (method.DeclaringType.GetTypeInfo().IsGenericType && method.DeclaringType.GetGenericTypeDefinition() == typeof(WeakPropertyChangedEventHandler<>))
            {
                return eventHandler;
            }

            var wehType = typeof(WeakPropertyChangedEventHandler<>).MakeGenericType(method.DeclaringType);

            var wehConstructor = wehType.GetTypeInfo().DeclaredConstructors.First();

            var weh = (IWeakPropertyChangedEventHandler)wehConstructor.Invoke(
                new object[] { eventHandler, unregister });

            return weh.Handler;
        }

        /// <summary>
        /// </summary>
        /// <typeparam name="E"> </typeparam>
        /// <param name="sourceHandler"> </param>
        /// <param name="value"> </param>
        /// <returns> </returns>
        public static EventHandler<E> Unregister<E>(this EventHandler<E> sourceHandler, EventHandler<E> value) where E : EventArgs
        {
            Contract.Requires<ArgumentNullException>(value != null, "value");
            Contract.Requires<ArgumentException>(!value.GetMethodInfo().IsStatic && value.Target != null, "Only instance methods are supported.");
            
            if (sourceHandler != null)
            {
                // look for the weak event handler in the invocation list
                foreach (EventHandler<E> evt in sourceHandler.GetInvocationList())
                {
                    var weh = evt.Target as IWeakEventHandler<E>;
                    if (weh != null)
                    {
                        var target = weh.Target.Target;
                        if (!ReferenceEquals(target, null) && ReferenceEquals(target, value.Target))
                        {
                            return weh.Handler;
                        }
                    }
                }
            }

            // return the input as the default if we don't find a wrapped event handler
            return value;
        }
    }
}